#include "SocketServer.h"
#include "ThreadPool.h"
#include "NamedIndex.h"
#include <atlbase.h>

#include "Utils.h"
#include "Exception.h"
#include "NamedIndex.h"
#include "SocketAddress.h"
#include "CriticalSection.h"
using JetByteTools::Win32::CThreadPool;
using JetByteTools::Win32::CNamedIndex;
#include "TangoThreadPool.h"
using JetByteTools::Win32::CIOCompletionPort;
using JetByteTools::Win32::CNamedIndex;
using JetByteTools::Win32::_tstring;
using JetByteTools::Win32::ToString;
#include "TangoThreadPool.h"
#include "TangoThreadPoolWorkerThread.h"
using JetByteTools::Win32::CIOBuffer;
using JetByteTools::Win32::CIOCompletionPort;
using JetByteTools::Win32::CCriticalSection;
using JetByteTools::Win32::CEvent;
//using JetByteTools::Win32::CSocketServer;
using JetByteTools::Win32::CException;
using JetByteTools::Win32::Output;
using JetByteTools::Win32::_tstring;
using JetByteTools::Win32::CSocketAddress;
using JetByteTools::Win32::ToString;
using JetByteTools::Win32::DumpData;
#pragma pack(push)
#pragma pack(8)
#pragma pack(pop)
typedef JetByteTools::Win32::CSocketServer::Socket Socket;

//-------------------------------------------------------//

#include "TangoPackage.h"
#include "pkghead.h"
#include <mstcpip.h>
#include <thread>
#include <mutex>

bool isip(const char* str);
bool SendLenData(SOCKET socket, const char* buf, int len);
bool SetTCPKeepAlive(SOCKET socket);
void ConnectThread(std::shared_ptr<tg::PackageRead> pkg, Socket *pSocket);

/*struct ConnectInfo {
	SOCKET lsocket;
	Socket *clientsocket;
	sockaddr_in remoteaddr;
	unsigned short len;
	const char* httphead;
	//-------------------//
	ConnectInfo() : httphead(nullptr) {}

	~ConnectInfo() {
		if (httphead) {
			delete[] httphead;
		}
	}

	void SetHttpHead(const char* buf, uint16_t size) {
		len = size;
		httphead = new char[len];
		memcpy((void*)httphead, buf, size);
	}
};

DWORD WINAPI ConnectThread(struct ConnectInfo *pinfo);
bool SingleSend(Socket *sock, char* buf, unsigned short slen);*/

//-------------------------------------------------------//

CTangoThreadPoolWorkerThread::CTangoThreadPoolWorkerThread(
	CIOCompletionPort &iocp,
	CEvent &messageReceivedEvent,
	CTangoThreadPool &pool)
	: JetByteTools::Win32::CThreadPool::WorkerThread(iocp, messageReceivedEvent, pool),
	m_pool(pool)
{
}

bool CTangoThreadPoolWorkerThread::Initialise()
{
	//global::LogMess(_T("CThreadPoolWorkerThread::Initialise"));

	return true;
}

static int fnvHash(char *data, int len)
{
	int seed = 0x1000193;
	int hash = 0x811c9dc5;

	for (int i = 0; i < len; i++)
	{
		byte b = data[i];
		hash = (hash ^ b) * 0x1000193;
	}

	hash += hash << 7;
	hash ^= hash >> 6;
	hash += hash << 3;
	hash ^= hash >> 8;
	hash += hash << 1;
	return hash;
}

void CTangoThreadPoolWorkerThread::Process(
	ULONG_PTR completionKey,
	DWORD operation,
	OVERLAPPED *pOverlapped)
{
	Socket *pSocket = reinterpret_cast<Socket *>(completionKey);
	CIOBuffer *pBuffer = static_cast<CIOBuffer *>(pOverlapped);

	try
	{
		switch (operation)
		{
		case CTangoThreadPool::ConnectionEstablished:
			OnConnectionEstablished(pSocket, pBuffer);
			break;

		case CTangoThreadPool::ReadCompleted:
			ProcessMessage(pSocket, pBuffer);
			break;

		case CTangoThreadPool::ConnectionClosing:
			OnConnectionClosing(pSocket);
			break;

		default:
			// do nothing
			break;
		}
	}
	catch (const CException &e)
	{
		//global::LogMess(string(_T("Process - Exception - ") + e.GetWhere() + _T(" - ") + e.GetMessage()).c_str());
		pSocket->Shutdown();
	}
	catch (...)
	{
		//global::LogMess(_T("Process - Unexpected exception"));
		pSocket->Shutdown();
	}

	pSocket->Release();

	if (pBuffer)
	{
		pBuffer->Release();
	}
}

void CTangoThreadPoolWorkerThread::Shutdown()
{
	//global::LogMess(_T("CThreadPoolWorkerThread::Shutdown"));
}

class CExportClass
{
public:
	int GetData() const
	{
		return m_Data;
	}

	void SetData(int Data)
	{
		m_Data = Data;
	}
protected:
private:
	int m_Data;
};

void CTangoThreadPoolWorkerThread::OnConnectionEstablished(
	Socket *pSocket,
	CIOBuffer *pAddress)
{
	//local::inst();
	CTangoThreadPool::CPerConnectionData *pData = m_pool.GetPerConnectionData(pSocket);

	//lint -e{826} Suspicious pointer-to-pointer conversion (area too small)
	CSocketAddress address(reinterpret_cast<const sockaddr*>(pAddress->GetBuffer()));

	pData->SetAddress(address.AsString());

	USES_CONVERSION;

	// 	std::string welcomeMessage("Welcome to large packet echo server ");
	// 
	// 	welcomeMessage = welcomeMessage + T2A(const_cast<TCHAR*>(pData->GetConnectionDetails().c_str())) + "\r\n";
	// 
	// 	pSocket->Write(welcomeMessage.c_str(), welcomeMessage.length());

	for (size_t i = 0; i < m_pool.m_numReads; ++i)
	{
		pSocket->Read();
	}

	//lint -e{1762} Member function could be made const
	//lint -e{818} Pointer parameter 'pAddress' (line 215) could be declared as pointing to const
}
void CTangoThreadPoolWorkerThread::OnConnectionClosing(
	Socket *pSocket)
{
	//local::inst().LogOut(pSocket->m_lId,pSocket);
	// We'll perform a lingering close on this thread
	pSocket->Close();
	//lint -e{1762} Member function could be made const
}

void CTangoThreadPoolWorkerThread::ProcessMessage(
	Socket *pSocket,
	CIOBuffer *pBuffer) const
{
	if (!pSocket->IsConnected(SD_SEND))
	{
		return;
	}

	CTangoThreadPool::CPerConnectionData *pData = m_pool.GetPerConnectionData(pSocket);
	pData->UseConnection();

	pBuffer->AddRef();

	if (pSocket->IsConnected(SD_SEND) && pBuffer)
	{
		auto pkg = std::make_shared<tg::PackageRead>( 
				  (const char*)pBuffer->GetBuffer()
				, pBuffer->GetUsed() );
		if (!pkg->IsOK()) {
			// package error or unknown action
			pSocket->Close();
			return;
		}

		switch (pkg->GetAction()) {
			case tg::Package::eAction::request: {
				/*struct ConnectInfo* pinfo = new ConnectInfo;
				pinfo->lsocket = pkg->GetLocalSocket();
				pinfo->clientsocket = pSocket;
				pinfo->remoteaddr = pkg->GetRemoteAddr();
				pinfo->SetHttpHead(
					  pkg->GetRequestDataBuffer()
					, pkg->GetRequestDataLength() );
				// thread
				auto td = std::thread(ConnectThread, pinfo);
				td.detach();*/

				// no thread
				//ConnectThread(pinfo);
				
				auto td = std::thread(ConnectThread, pkg, pSocket);
				td.detach();
			} break;
			case tg::Package::eAction::header: {
				SendLenData(pkg->GetRemoteSocket()
						, pkg->GetDataBuffer()
						, pkg->GetDataLength() );
			} break;
			case tg::Package::eAction::dis_conn: {
				closesocket(pkg->GetRemoteSocket());
			} break;
			case tg::Package::eAction::dis_conn_client: {} break;
			case tg::Package::eAction::had_data: {} break;
		}
	}

	pBuffer->Release();
}

void SendErrorPkg(std::shared_ptr<tg::PackageRead> pkg, Socket *pSocket, tg::PackageWrite &pkgwrite) {
	pkgwrite.Reset();
	pkgwrite.SetAction(tg::Package::eAction::dis_conn_client);
	pkgwrite.SetLocalSocket(pkg->GetLocalSocket());
	pkgwrite.SetRemoteSocket(pkg->GetRemoteSocket());
	pkgwrite.SetDataLength(0);
	pkgwrite.Commit();
	pSocket->Write(pkgwrite.GetDataBuffer(), pkgwrite.GetDataLength());
}

void ConnectThread(std::shared_ptr<tg::PackageRead> pkg, Socket *pSocket) {
	tg::PackageWrite pkgwrite;	
	SOCKET remotesocket = socket(AF_INET, SOCK_STREAM, 0);
	const char* data_buf = pkg->GetRequestDataBuffer();
	uint16_t data_len = pkg->GetRequestDataLength();
	std::string host_url = tg::GetServerName(data_buf, data_len);
	sockaddr_in addr_in = pkg->GetRemoteAddr();

	printf("domain name is %s\r\n", host_url.c_str());
	
	if (host_url.length() == 0) {
		if (connect(remotesocket, (sockaddr*)&addr_in, sizeof(sockaddr_in)) < 0) {
			//error to connect, notify
			SendErrorPkg(pkg, pSocket, pkgwrite);
			closesocket(remotesocket);
			return;
		}

		if (!SendLenData(remotesocket, data_buf, data_len)) {
			//error to connect, notify
			SendErrorPkg(pkg, pSocket, pkgwrite);
			closesocket(remotesocket);
			return;
		}
	} else {
		if (isip(host_url.c_str())) {
			addr_in.sin_addr.S_un.S_addr = inet_addr(host_url.c_str());
			pkg->SetRemoteAddr(addr_in);
			if (connect(remotesocket, (sockaddr*)&addr_in, sizeof(sockaddr_in)) < 0) {
				//error to connect, notify
				SendErrorPkg(pkg, pSocket, pkgwrite);
				closesocket(remotesocket);
				return;
			}
			if (!SendLenData(remotesocket, data_buf, data_len)) {
				//error to connect, notify
				SendErrorPkg(pkg, pSocket, pkgwrite);
				closesocket(remotesocket);
				return;
			}
		} else {
			hostent *phst = gethostbyname(host_url.c_str());
			if (phst) {
				addr_in.sin_addr = *(in_addr*)phst->h_addr;
				pkg->SetRemoteAddr(addr_in);
				if (connect(remotesocket, (sockaddr*)&addr_in, sizeof(sockaddr_in)) < 0) {
					//error to connect, notify
					SendErrorPkg(pkg, pSocket, pkgwrite);
					closesocket(remotesocket);
					return;
				}
				if (!SendLenData(remotesocket, data_buf, data_len)) {
					//error to connect, notify
					SendErrorPkg(pkg, pSocket, pkgwrite);
					closesocket(remotesocket);
					return;
				}
			} else {
				//error to connect, notify
				/*
				*	package length, action, local socket, remote socket, data length, data
				*	request->header->disconnected->disconnected from client->return data
				*/

				SendErrorPkg(pkg, pSocket, pkgwrite);
				closesocket(remotesocket);
				return;
			}
		}
	}

	printf("success to connect.\r\n");
	SetTCPKeepAlive(remotesocket);

	char buf[64 * 1024] = { 0 };
	// cause head need space,so erase 200 for head data.
	int len = 64 * 1024 - 200;
	while (1) {
		int rlen = recv(remotesocket, buf, len, 0);
		if (rlen <= 0) {
			printf("socket %d disconnected error %d\r\n", (int)remotesocket, GetLastError());
			SendErrorPkg(pkg, pSocket, pkgwrite);
			closesocket(remotesocket);
			return;
		} else {
			printf("got data from websit. \r\n");
			pkgwrite.Reset();
			pkgwrite.SetAction(tg::Package::eAction::had_data);
			pkgwrite.SetLocalSocket(pkg->GetLocalSocket());
			pkgwrite.SetRemoteSocket(remotesocket);
			pkgwrite.SetDataLength(rlen);
			pkgwrite.SetData(buf);
			pkgwrite.Commit();
#ifdef DEBUG
			/*pkgwrite.Dump();
			printf("raw data -------->>>\n");
    		printf("%s\n", buf);
			printf("raw data end-----------");*/
#endif

			pSocket->Write(pkgwrite.GetDataBuffer(), pkgwrite.GetDataLength());
		}
	}
	closesocket(remotesocket);
	return;
}
/*
DWORD WINAPI ConnectThread(struct ConnectInfo *pinfo) {
	char buf[64 * 1024] = { 0 };
	char sendbuf[64 * 1024] = { 0 };
	int len = 64 * 1024 - 200;
	int sendlen = 0;
	unsigned short slen = 0;
	SOCKET remotesocket = socket(AF_INET, SOCK_STREAM, 0);
	char url[1024] = { 0 };

	{
		std::string sername = tg::GetServerName(pinfo->httphead, pinfo->len);
		memcpy(url, sername.data(), sername.size());
	}
	printf("domain name is %s\r\n", url);
	
	if (strlen(url) == 0)
	{
		if (connect(remotesocket, (sockaddr*)&pinfo->remoteaddr, sizeof(sockaddr_in)) < 0)
		{
			//error to connect, notify
			sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
			sendbuf[0] = 4;
			*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
			*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
			*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
			slen = sendlen;
			SingleSend(pinfo->clientsocket, sendbuf, slen);
			closesocket(remotesocket);
			goto outthread;
			return 0;
		}
		if (!SendLenData(remotesocket, pinfo->httphead, pinfo->len))
		{
			//error to connect, notify
			sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
			sendbuf[0] = 4;
			*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
			*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
			*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
			slen = sendlen;
			SingleSend(pinfo->clientsocket, sendbuf, slen);
			closesocket(remotesocket);
			goto outthread;
			return 0;
		}
	}
	else
	{
		if (isip(url))
		{
			pinfo->remoteaddr.sin_addr.S_un.S_addr = inet_addr(url);
			if (connect(remotesocket, (sockaddr*)&pinfo->remoteaddr, sizeof(sockaddr_in)) < 0)
			{
				//error to connect, notify
				sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
				sendbuf[0] = 4;
				*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
				*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
				*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
				slen = sendlen;
				SingleSend(pinfo->clientsocket, sendbuf, slen);
				closesocket(remotesocket);
				goto outthread;
				return 0;
			}
			if (!SendLenData(remotesocket, pinfo->httphead, pinfo->len))
			{
				//error to connect, notify
				sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
				sendbuf[0] = 4;
				*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
				*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
				*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
				slen = sendlen;
				SingleSend(pinfo->clientsocket, sendbuf, slen);
				closesocket(remotesocket);
				goto outthread;
				return 0;
			}
		}
		else
		{
			hostent *phst = gethostbyname(url);
			if (phst)
			{
				pinfo->remoteaddr.sin_addr = *(in_addr*)phst->h_addr;
				if (connect(remotesocket, (sockaddr*)&pinfo->remoteaddr, sizeof(sockaddr_in)) < 0)
				{
					//error to connect, notify
					sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
					sendbuf[0] = 4;
					*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
					*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
					*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
					slen = sendlen;
					SingleSend(pinfo->clientsocket, sendbuf, slen);
					closesocket(remotesocket);
					goto outthread;
					return 0;
				}
				if (!SendLenData(remotesocket, pinfo->httphead, pinfo->len))
				{
					//closesocket(clientsocket);
					//error to connect, notify
					sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
					sendbuf[0] = 4;
					*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
					*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
					*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
					slen = sendlen;
					SingleSend(pinfo->clientsocket, sendbuf, slen);
					closesocket(remotesocket);
					goto outthread;
					return 0;
				}
			}
			else
			{
				//error to connect, notify
				// package length, action, local socket, remote socket, data length, data
				// request->header->disconnected->disconnected from client->return data
				sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
				sendbuf[0] = 4;
				*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
				*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
				*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
				slen = sendlen;
				SingleSend(pinfo->clientsocket, sendbuf, slen);
				closesocket(remotesocket);
				goto outthread;
				return 0;
			}
		}
	}
	printf("success to connect.\r\n");
	SetTCPKeepAlive(remotesocket);
	while (1)
	{
		int rlen = 0;
		rlen = recv(remotesocket, buf, len, 0);
		if (rlen <= 0)
		{
			printf("socket %d disconnected error %d\r\n", (int)remotesocket, GetLastError());
			sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + 0;
			sendbuf[0] = 4;
			*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
			*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
			*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = 0;
			slen = sendlen;
			SingleSend(pinfo->clientsocket, sendbuf, slen);
			break;
		}
		else
		{
			printf("got data from websit. \r\n");
			sendlen = sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short) + rlen;
			sendbuf[0] = 5;
			*(SOCKET*)&sendbuf[sizeof(char)] = pinfo->lsocket;
			*(SOCKET*)&sendbuf[sizeof(char) + sizeof(SOCKET)] = remotesocket;
			*(unsigned short*)&sendbuf[sizeof(char) + 2 * sizeof(SOCKET)] = rlen;
			memcpy(sendbuf + sizeof(char) + 2 * sizeof(SOCKET) + sizeof(unsigned short), buf, rlen);
			unsigned short slen = sendlen;
			SingleSend(pinfo->clientsocket, sendbuf, slen);
		}
	}
	outthread:
	closesocket(remotesocket);
	delete pinfo;
	return 0;
}*/

bool SendLenData(SOCKET socket, const char* buf, int len) {
	bool ret = false;
	int slen = 0;
	int alllen = 0;
	while (alllen < len)
	{
		slen = send(socket, buf + alllen, len - alllen, 0);
		if (slen > 0)
		{
			alllen += slen;
			if (alllen == len)
			{
				ret = true;
				break;
			}
		}
		else
		{
			printf("send error %d \r\n",GetLastError());
			break;
		}
	}
	return ret;
}

bool isip(const char* str) {
	bool ret = true;
	size_t len = strlen(str);
	for (size_t i = 0; i < len; i++)
	{
		if (str[i] >= '0' && str[i] <= '9')
		{

		}
		else if (str[i] == '.')
		{
		}
		else
		{
			ret = false;
			break;
		}
	}
	return ret;
}

bool SetTCPKeepAlive(SOCKET socket) {
	unsigned delay = 45 * 1000;
	struct tcp_keepalive keepalive_vals = {
		1u,  // TCP keep-alive on.
		delay,  // Delay seconds before sending first TCP keep-alive packet.
		delay,  // Delay seconds between sending TCP keep-alive packets.
	};
	DWORD bytes_returned = 0xABAB;
	int rv = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &keepalive_vals,
		sizeof(keepalive_vals), NULL, 0,
		&bytes_returned, NULL, NULL);
	//int os_error = WSAGetLastError();
	// Disregard any failure in disabling nagle or enabling TCP Keep-Alive.
	return rv == 0;
}

bool SingleSend(Socket *sock, char* buf, unsigned short slen) {
	//std::lock_guard<std::mutex> lock(g_mutex_);
	//bok1 = sock->Write((char*)&slen, sizeof(uint16_t));
	//bok2 = sock->Write(buf, slen);
	uint16_t len = slen + sizeof(uint16_t);
	auto buffer = std::shared_ptr<char>(
		  new char[len]
		, [](char *p) {
			delete[] p;
		});
	memcpy(buffer.get(), (const void*)&slen, sizeof(uint16_t));
	memcpy(buffer.get() + sizeof(uint16_t), buf, slen);
	return sock->Write(buffer.get(), len);
}